#include "TerrainManager.h"

void TerrainManager::SaveParams( D3DXMATRIX *wmat, double scale, UINT level, double viewap, bool bfog ) {
	D3DXMATRIX imat;

	//==============================================================
	//			Render parameters
	//==============================================================

	RP.dist_scale = scale;
	RP.tgtlevel = min( level, maxlvl );
	RP.mWorld = *wmat;

	D3DMAT_MatrixInvert( &imat, wmat );
	RP.cdir = _V( imat._41, imat._42, imat._43 );	//camera position in planet coords (Rad = 1.0)
	normalise( RP.cdir );

	//scale world matrix back.
	RP.mWorld._11 /= 1000.0f;	RP.mWorld._12 /= 1000.0f;	RP.mWorld._13 /= 1000.0f;
	RP.mWorld._21 /= 1000.0f;	RP.mWorld._22 /= 1000.0f;	RP.mWorld._23 /= 1000.0f;
	RP.mWorld._31 /= 1000.0f;	RP.mWorld._32 /= 1000.0f;	RP.mWorld._33 /= 1000.0f;
	RP.mWorld_tmp = RP.mWorld;
	
	RP.cpos = OBJ->CPos*scale;
	RP.bFog = bfog;

	RP.grot = OBJ->mROT_new;
	RP.grot *= scale;

	RP.bCockpit = (oapiCameraMode() == CAM_COCKPIT);
	RP.objsize = OBJ->rad;
	RP.cdist = OBJ->CDist/(OBJ->rad);
	RP.viewap = (viewap ? viewap : acos(1.0/max(1.0, RP.cdist)) );
	if( CFG->bHeightMap || CFG->bHeightMapGenerator )		RP.viewap += 0.1;
	RP.sdir = tmul( RP.grot, -OBJ->GPos_new );
	RP.horzdist = sqrt( RP.cdist*RP.cdist - 1.0 )*RP.objsize;
	normalise( RP.sdir );

	//==============================================================
	//			Limit max resolution for fast movement
	//==============================================================
	double limitstep, cstep = acos( dotp( RP.cdir, pcdir ) );
	UINT8 maxlevel = MAX_RESOLUTION;
	static double limitstep0 = 5.12*pow( 2.0, -(double)MAX_RESOLUTION );
	for( limitstep = limitstep0; cstep > limitstep && maxlevel > 5; limitstep *= 2.0 )
		maxlevel--;
	RP.tgtlevel = min( RP.tgtlevel, maxlevel );
	oapiLocalToEqu( obj, RP.cdir, &RP.clng, &RP.clat, &RP.crad );

	pcdir = RP.cdir;	//save previous camera direction

	//==================================================
	//		NEW
	//==================================================
	if( RP.tgtlevel <= 4 )	return;

	int startlevel = min( RP.tgtlevel, 8 ), idx, ilng;;
	INT	nlat = NLAT[startlevel], ilat;
	UINT *nlng = NLNG[startlevel];
	UINT8 hemisphere;
	TILEDESC1 *td = tiledesc + patchidx[startlevel - 1];

	ntile = 0;
	ntile_det = 0;
	static TEXCRDRANGE1 range = { 0.0f, 1.0f, 0.0f, 1.0f };
	TILETEX Tex;
	TILECRDRANGE RANGE;
	RANGE.range_tex = range;
	RANGE.range_norm = range;
	RANGE.range_height = range;
	RANGE.range_lcover = range;

	//sync with loading, L8 generation, and L9-L18 heightmap generation threads.
	EnterCriticalSection( &tload->LoadingCS );

	//save necessary parameters for details renderer:
	mWorld_north = RP.mWorld;
	grot_north = RP.grot;

	for( hemisphere = idx = 0; hemisphere < 2; hemisphere++ ) {
		if( hemisphere ) {
			D3DXMatrixMultiply( &RP.mWorld, &RSouth, &RP.mWorld );
			RP.mWorld_tmp = RP.mWorld;
			RP.grot.m12 = -RP.grot.m12;
			RP.grot.m13 = -RP.grot.m13;
			RP.grot.m22 = -RP.grot.m22;
			RP.grot.m23 = -RP.grot.m23;
			RP.grot.m32 = -RP.grot.m32;
			RP.grot.m33 = -RP.grot.m33;
		}
		for( ilat = nlat-1; ilat >= 0; ilat-- ) {
			for( ilng = 0; ilng < nlng[ilat]; ilng++ ) {
				TILEDESC1 *_td = td + idx;
				Tex.tex = _td->tex.get();
				Tex.ltex = _td->ltex;
				Tex.ntex = _td->ntex.get();
				Tex.htex = _td->htex.get();
				Tex.ctex = _td->ctex;
				ProcessTile( hemisphere, startlevel, ilat, nlat, ilng, nlng[ilat], _td,
					RANGE, Tex, _td->flags, RANGE, Tex, _td->flags );
				idx++;
			}
		}
	}

	if( RP.tgtlevel > 7 )	CreateShadowsTileList();
	CreateCameraTileList();

	LeaveCriticalSection( &tload->LoadingCS );
}

void TerrainManager::RenderTerrainShadows()
{
	//there's no terrain on levels < 8 in this version of terrain engine
	if( RP.tgtlevel < 8 )	return;

	float alt = OBJ->CDist - OBJ->rad;
	alt = alt > CFG->minNormalAlt ? (alt < CFG->maxTerrainAlt ? (CFG->maxTerrainAlt - alt)/(CFG->maxTerrainAlt - CFG->minNormalAlt) : 0.0 ) : 1.0;
	
	rend->RenderTerrainShadows( tiles_shadows, RP.dist_scale, alt );
}

void TerrainManager::Render() {


	auto luminance = this->OBJ->GetScene()->GetLuminance();
	UINT
		level = RP.tgtlevel,
		startlevel = min( RP.tgtlevel, 8 ), idx, ilng;
	TILEDESC1 *td = tiledesc + patchidx[startlevel - 1];

	float alt = OBJ->CDist - OBJ->rad;
	alt = alt > CFG->minNormalAlt ? (alt < CFG->maxTerrainAlt ? (CFG->maxTerrainAlt - alt)/(CFG->maxTerrainAlt - CFG->minNormalAlt) : 0.0 ) : 1.0;
	
	if( RP.tgtlevel <= 4 ) {
		rend->RenderSphere( RP.tgtlevel, patchidx[level] - patchidx[level - 1], td, &RP.mWorld, RP.dist_scale, luminance );
		return;
	}

	//==============================================================
	//		L7-L18
	//==============================================================
/*	INT	nlat = NLAT[startlevel], ilat;
	UINT *nlng = NLNG[startlevel];
	UINT8 hemisphere;

	ntile = 0;
	ntile_det = 0;
	static TEXCRDRANGE1 range = { 0.0f, 1.0f, 0.0f, 1.0f };
	TILETEX Tex;
	TILECRDRANGE RANGE;
	RANGE.range_tex = range;
	RANGE.range_norm = range;
	RANGE.range_height = range;
	RANGE.range_lcover = range;

	//sync with loading, L8 generation, and L9-L18 heightmap generation threads.
	EnterCriticalSection( &tload->LoadingCS );
	EnterCriticalSection( &tgen->L5GeneratorCS );
	EnterCriticalSection( &tgen->GeneratorCS );

	//save necessary parameters for details renderer:
	mWorld_north = RP.mWorld;
	grot_north = RP.grot;

	for( hemisphere = idx = 0; hemisphere < 2; hemisphere++ ) {
		if( hemisphere ) {
			D3DXMatrixMultiply( &RP.mWorld, &RSouth, &RP.mWorld );
			RP.mWorld_tmp = RP.mWorld;
			RP.grot.m12 = -RP.grot.m12;
			RP.grot.m13 = -RP.grot.m13;
			RP.grot.m22 = -RP.grot.m22;
			RP.grot.m23 = -RP.grot.m23;
			RP.grot.m32 = -RP.grot.m32;
			RP.grot.m33 = -RP.grot.m33;
		}
		for( ilat = nlat-1; ilat >= 0; ilat-- ) {
			for( ilng = 0; ilng < nlng[ilat]; ilng++ ) {
				TILEDESC1 *_td = td + idx;
				Tex.tex = _td->tex.get();
				Tex.ltex = _td->ltex;
				Tex.ntex = _td->ntex.get();
				Tex.htex = _td->htex.get();
				Tex.ctex = _td->ctex;
				ProcessTile( hemisphere, startlevel, ilat, nlat, ilng, nlng[ilat], _td,
					RANGE, Tex, _td->flags, RANGE, Tex, _td->flags );
				idx++;
			}
		}
	}

	CreateShadowsTileList();
	CreateCameraTileList();*/

	if( tiles_camera.size() )
	{
		if( RP.tgtlevel > 7 )	rend->RenderHighResTiles( &tiles_camera[0], tiles_camera.size(), RP.dist_scale, alt, luminance );
		else					rend->RenderLowResTiles( &tiles_camera[0], tiles_camera.size(), RP.dist_scale, luminance );
	}

	tiles_noculling.clear();
	tiles_shadows.clear();
	tiles_camera.clear();
}

//============================================================================
//								ProcessTile
//============================================================================

void TerrainManager::ProcessTile(
		UINT8 hemisphere, UINT8 level, UINT ilat, UINT nlat, UINT ilng, UINT nlng, TILEDESC1 *td,
		TILECRDRANGE &RANGE, TILETEX Tex, DWORD flag,
		TILECRDRANGE &bkp_RANGE, TILETEX bkp_Tex, DWORD bkp_flag )
{
	inline void TileCenter( UINT8 hemisphere, UINT ilat, UINT nlat, UINT ilng, UINT nlng, VECTOR3 &cnt );
	inline void TileExtents( UINT8 hemisphere, UINT ilat, UINT nlat, UINT ilng, UINT nlng, double &lat1, double &lat2, double &lng1, double &lng2 );

	static const double rad0 = sqrt(2.0)*PI05*0.5;
	VECTOR3 cnt;
	D3DXMATRIX mWorld;

	//=======================================================
	//			Visibility check
	//=======================================================

	double rad = rad0/(double)nlat;
	TileCenter( hemisphere, ilat, nlat, ilng, nlng, cnt );
	double adist = acos(dotp( RP.cdir, cnt )) - rad;
	if( adist >= RP.viewap )
		return;

	TileWorldMatrix( ilat, nlat, ilng, nlng, mWorld );

	if( td->flags & RECOMPUTEBS )
		ComputeTileBoundingSphere( td, hemisphere, ilat, nlat, nlng );

//	if( !IsTileVisible( td, &mWorld, RP.dist_scale ) ) {
//		DeleteSubTiles( td );
//		return;
//	}
	
	//=======================================================
	//			Stepdown
	//=======================================================

	bool
		bStepDown = (level < RP.tgtlevel ),
		bCoarseTex = false;

	if( bStepDown && adist > 0.0 ) {
		double lat1, lat2, lng1, lng2, adist_lng, adist_lat, adist2;
		TileExtents( hemisphere, ilat, nlat, ilng, nlng, lat1, lat2, lng1, lng2 );

		if( RP.clng < lng1-PI )			RP.clng += PI2;
		else if( RP.clng > lng2+PI )	RP.clng -= PI2;

		if( RP.clng < lng1 )			adist_lng = lng1 - RP.clng;
		else if( RP.clng > lng2 )		adist_lng = RP.clng - lng2;
		else							adist_lng = 0.0;

		if( RP.clat < lat1 )			adist_lat = lat1 - RP.clat;
		else if( RP.clat > lat2 )		adist_lat = RP.clat - lat2;
		else							adist_lat = 0.0;

		adist2 = adist_lng + adist_lat;//max( adist_lng, adist_lat );

		double cosa = cos( adist2 );
		double a = sin( adist2 );
		double b = RP.cdist - cosa;
		double ctilt = b*cosa/sqrt(a*a*(1.0 + 2.0*b) + b*b);
		if( adist2 > rad*(2.0*ctilt + 0.3) ) {
			bStepDown = false;
			if( adist2 > rad*(4.2*ctilt + 0.3) )
				bCoarseTex = true;
		}
	}
	
//	if( !td->htex )
//		if( bHeightMapGenerator )
//			tgen->AddToGenQueue( this, td, hemisphere, ilat, nlat, ilng, nlng );

	//=======================================================
	//			Add specified level to Details tile list
	//=======================================================
/*
	if( level == 8 ) {
		if( ntile_det == tbufsize_det )		ExpandDetailsTRSBuffer();

		DetailsTileList[ntile_det].level = level;
		DetailsTileList[ntile_det].hemisphere = hemisphere;
		DetailsTileList[ntile_det].ilat = ilat;
		DetailsTileList[ntile_det].nlat = nlat;
		DetailsTileList[ntile_det].ilng = ilng;
		DetailsTileList[ntile_det].nlng = nlng;
		DetailsTileList[ntile_det].td = td;
		DetailsTileList[ntile_det].RANGE = RANGE;
		DetailsTileList[ntile_det].Tex = Tex;
		ntile_det++;
	}
*/
	//============================================
	//			Step down or render
	//============================================

	if( bStepDown ) {
		//increase resolution
		INT i;
		UINT j, idx = 0;

		float du[4] = {
			(RANGE.range_tex.tumax - RANGE.range_tex.tumin)*0.5f,
			(RANGE.range_norm.tumax - RANGE.range_norm.tumin)*0.5f,
			(RANGE.range_height.tumax - RANGE.range_height.tumin)*0.5f,
			(RANGE.range_lcover.tumax - RANGE.range_lcover.tumin)*0.5f
		};

		float dv[4] = {
			(RANGE.range_tex.tvmax - RANGE.range_tex.tvmin)*0.5f,
			(RANGE.range_norm.tvmax - RANGE.range_norm.tvmin)*0.5f,
			(RANGE.range_height.tvmax - RANGE.range_height.tvmin)*0.5f,
			(RANGE.range_lcover.tvmax - RANGE.range_lcover.tvmin)*0.5f
		};

		TILECRDRANGE SUBRANGE;
		static TEXCRDRANGE1 fullrange = { 0.0f, 1.0f, 0.0f, 1.0f };
		for( i = 1; i >= 0; i-- ) {

			SUBRANGE.range_tex.tvmax = (SUBRANGE.range_tex.tvmin = RANGE.range_tex.tvmin + (1-i)*dv[0]) + dv[0];
			SUBRANGE.range_norm.tvmax = (SUBRANGE.range_norm.tvmin = RANGE.range_norm.tvmin + (1-i)*dv[1]) + dv[1];
			SUBRANGE.range_height.tvmax = (SUBRANGE.range_height.tvmin = RANGE.range_height.tvmin + (1-i)*dv[2]) + dv[2];
			SUBRANGE.range_lcover.tvmax = (SUBRANGE.range_lcover.tvmin = RANGE.range_lcover.tvmin + (1-i)*dv[3]) + dv[3];

			for( j = 0; j < 2; j++ ) {

				SUBRANGE.range_tex.tumax = (SUBRANGE.range_tex.tumin = RANGE.range_tex.tumin + j*du[0]) + du[0];
				SUBRANGE.range_norm.tumax = (SUBRANGE.range_norm.tumin = RANGE.range_norm.tumin + j*du[1]) + du[1];
				SUBRANGE.range_height.tumax = (SUBRANGE.range_height.tumin = RANGE.range_height.tumin + j*du[2]) + du[2];
				SUBRANGE.range_lcover.tumax = (SUBRANGE.range_lcover.tumin = RANGE.range_lcover.tumin + j*du[3]) + du[3];

				bool bIsFull = true;
				TILEDESC1 *subtile = td->subtile[idx];
				
				if( !subtile ) {
					td->subtile[idx] = subtile = NewTile( td->amin, td->amax );
					bIsFull = false;
				}
				else {
				//	if( !(subtile->flags & 696) )	goto skip;//nothing to load

					if( subtile->flags & LOAD_TEX ) {
						if( !(td->flags & LOAD_TEX) ) {
							tload->LoadTile( this, subtile );
						}
						bIsFull = false;
					//	goto skip;
					}

					if( subtile->flags & LOAD_NORM )
						if( !(td->flags & LOAD_NORM ) )
							tload->LoadTile( this, subtile );

					if( subtile->flags & LOAD_LCOVER )//<= this will not work well with land cover...
						if( !(td->flags & LOAD_LCOVER ) )
							tload->LoadTile( this, subtile );
				}

				if( bIsFull )	bIsFull = (subtile->tex != nullptr && subtile->tex->IsLoaded());
				TILECRDRANGE nRANGE, nSUBRANGE;
				TILETEX nTex, bkp_nTex;

				//normal map
				if( !(subtile->flags & LOAD_NORM) && subtile->ntex ) {
					bkp_nTex.ntex = nTex.ntex = subtile->ntex.get();
					nSUBRANGE.range_norm = nRANGE.range_norm = fullrange;
				}
				else {
					bkp_nTex.ntex = nTex.ntex = Tex.ntex;
					nSUBRANGE.range_norm = nRANGE.range_norm = SUBRANGE.range_norm;
				}

				//height map
				if( !(subtile->flags & LOAD_HEIGHT) && subtile->htex ) {
					bkp_nTex.htex = nTex.htex = subtile->htex.get();
					nSUBRANGE.range_height = nRANGE.range_height = fullrange;
				}
				else {
					bkp_nTex.htex = nTex.htex = Tex.htex;
					nSUBRANGE.range_height = nRANGE.range_height = SUBRANGE.range_height;
				}

				//land cover
				if( !(subtile->flags & LOAD_LCOVER) && subtile->ctex ) {
					bkp_nTex.ctex = nTex.ctex = subtile->ctex;
					nSUBRANGE.range_lcover = nRANGE.range_lcover = fullrange;
				}
				else {
					bkp_nTex.ctex = nTex.ctex = Tex.ctex;
					nSUBRANGE.range_lcover = nRANGE.range_lcover = SUBRANGE.range_lcover;
				}

				//output to next L
				if( bIsFull ) {
					nTex.tex = subtile->tex.get();
					nTex.ltex = subtile->ltex;
					bkp_nTex.tex = Tex.tex;
					bkp_nTex.ltex = Tex.ltex;
					nRANGE.range_tex = fullrange;
					nSUBRANGE.range_tex = SUBRANGE.range_tex;
					ProcessTile( hemisphere, level+1, ilat*2+i, nlat*2, ilng*2+j, nlng*2, subtile,
						nRANGE, nTex, subtile->flags,
						nSUBRANGE, bkp_nTex, flag );
				}
				else {
					bkp_nTex.tex = nTex.tex = Tex.tex;
					bkp_nTex.ltex = nTex.ltex = Tex.ltex;
					nRANGE.range_tex = SUBRANGE.range_tex;
					nSUBRANGE.range_tex = SUBRANGE.range_tex;
					ProcessTile( hemisphere, level+1, ilat*2+i, nlat*2, ilng*2+j, nlng*2, subtile,
						nRANGE, nTex, flag,
						nSUBRANGE, bkp_nTex, flag );
				}
				idx++;
			}
		}
	}
	else {
		if( ntile == tbufsize )		ExpandTRSBuffer();

		TILERENDERSPEC trs;
		if( bCoarseTex ) {
			trs.td = td;
			trs.hemisphere = hemisphere;
			trs.level = level;
			trs.ilat = ilat;
			trs.nlat = nlat;
			trs.ilng = ilng;
			trs.nlng = nlng;
			trs.mWorld = mWorld;
			trs.Tex = bkp_Tex;
			trs.RANGE = bkp_RANGE;
			trs.flag = bkp_flag;			
		}
		else {
			trs.td = td;
			trs.hemisphere = hemisphere;
			trs.level = level;
			trs.ilat = ilat;
			trs.nlat = nlat;
			trs.ilng = ilng;
			trs.nlng = nlng;
			trs.mWorld = mWorld;
			trs.Tex = Tex;
			trs.RANGE = RANGE;
			trs.flag = flag;
		}

		tiles_noculling.push_back( trs );
		ntile++;
	}
}

//============================================================================
//								Misc
//============================================================================

void TileCenter( UINT8 hemisphere, UINT ilat, UINT nlat, UINT ilng, UINT nlng, VECTOR3 &cnt ) {
	//returns tile center vector
	double cntlat = PI05*((double)ilat+0.5)/(double)nlat,		slat = sin(cntlat),		clat = cos(cntlat);
	double cntlng = PI2*((double)ilng+0.5)/(double)nlng + PI,	slng = sin(cntlng),		clng = cos(cntlng);

	if( hemisphere )	cnt.x = clat*clng,	cnt.y = -slat,	cnt.z = -clat*slng;
	else				cnt.x = clat*clng,	cnt.y = slat,	cnt.z = clat*slng;
}

void TerrainManager::TileWorldMatrix( UINT ilat, UINT nlat, UINT ilng, UINT nlng, D3DXMATRIX &mWorld ) {
	//returns tile-specific world matrix
//	void MatrixRotY1( D3DXMATRIX *out, float angle );

	D3DXMATRIX rtile, wtrans;
	double lng = PI2*(double)ilng/(double)nlng + PI;

	MatrixRotY1( &rtile, (float)lng );

	if( nlat > 8 ) {
		double lat = PI05*(double)ilat/(double)nlat;
		double s = RP.objsize;
		double dx = s*cos(lng)*cos(lat);
		double dy = s*sin(lat);
		double dz = s*sin(lng)*cos(lat);
		RP.mWorld_tmp._41 = (float)(dx*RP.grot.m11 + dy*RP.grot.m12 + dz*RP.grot.m13 + RP.cpos.x);
		RP.mWorld_tmp._42 = (float)(dx*RP.grot.m21 + dy*RP.grot.m22 + dz*RP.grot.m23 + RP.cpos.y);
		RP.mWorld_tmp._43 = (float)(dx*RP.grot.m31 + dy*RP.grot.m32 + dz*RP.grot.m33 + RP.cpos.z);
		D3DXMatrixMultiply( &mWorld, &rtile, &RP.mWorld_tmp );
	}
	else
		D3DXMatrixMultiply( &mWorld, &rtile, &RP.mWorld );
}

void MatrixRotY1( D3DXMATRIX *out, float angle ) {
	double sinr = sin( angle ), cosr = cos( angle );
	memset( out, 0, 64 );
	out->_11 = out->_33 = (float)cosr;
	out->_31 = -( out->_13 = (float)sinr );
	out->_22 = out->_44 = 1.0f;
}

void TileExtents( UINT8 hemisphere, UINT ilat, UINT nlat, UINT ilng, UINT nlng, double &lat1, double &lat2, double &lng1, double &lng2 ) {
	//returns latitudes and longtitudes of tile boundings
	lat1 = PI05 * (double)ilat/(double)nlat;
	lat2 = lat1 + PI05/(double)nlat;
	lng1 = PI2 * (double)ilng/(double)nlng + PI;
	lng2 = lng1 + PI2/nlng;

	if( hemisphere ) {
		double tmp = lat1;
		lat1 = -lat2;
		lat2 = -tmp;

		tmp = lng1;
		lng1 = -lng2;
		lng2 = -tmp;

		if( lng2 < 0 )
			lng1 += PI2, lng2 += PI2;
	}
}

void TerrainManager::ComputeTileBoundingSphere( TILEDESC1 *tile, UINT8 hemisphere, UINT ilat, UINT nlat, UINT nlng ) {
	//computes tile bounding sphere after tile's creation or loading of detailed heightmap [tile local coords]
	double mx, my, mz, ax, ay, az;

	mx = PI05*(double(ilat+1)/double(nlat));
	my = PI2/(double)nlng;
	mz = 1000.0 + double(tile->amax*1000.0)/OBJ->rad;
	D3DXVECTOR3 vmax = D3DXVECTOR3( mz*cos(my)*cos(mx), mz*sin(mx), mz*sin(my)*cos(mx) );

	ax = PI05*(double(ilat*2 + 1))/(double(nlat*2));
	ay = PI2/(double(nlng*2));
	az = 1000.0 + double((tile->amin + tile->amax)*500.0)/OBJ->rad;
	tile->bsPos = D3DXVECTOR3( az*cos(ay)*cos(ax), az*sin(ax), az*sin(ay)*cos(ax) );

	vmax -= tile->bsPos;
	tile->bsRad = D3DXVec3Length( &vmax );
	tile->flags &= ~RECOMPUTEBS;

	if( nlat > 8 ) {
		double minlat = PI05*((double)ilat/(double)nlat);
		double dx = 1000.0f*cos( minlat );
		double dy = 1000.0f*sin( minlat );

		tile->bsPos.x -= dx;
		tile->bsPos.y -= dy;
	}
}

bool TerrainManager::IsTileVisible( TILEDESC1 *tile, D3DXMATRIX *mWorld, float dist_scale ) {
	float rad = tile->bsRad*(OBJ->rad/1000.0f)*dist_scale;
	D3DXVECTOR3 vP;
	D3DXVec3TransformCoord( &vP, &tile->bsPos, mWorld );

	float dist = D3DXVec3Length( &vP );
	if( (dist - rad) > RP.horzdist )
		return false;
	return SC->IsVisibleInCamera( &vP, rad );
}

//============================================================================
//								Misc
//============================================================================

void TerrainManager::ExpandTRSBuffer() {
	TILERENDERSPEC *tmp = new TILERENDERSPEC [tbufsize+64];
	memcpy( tmp, TileList, sizeof(TILERENDERSPEC)*tbufsize );
	delete [ ] TileList;
	memset( tmp+tbufsize, 0, sizeof(TILERENDERSPEC)*64 );
	tbufsize += 64;
	TileList = tmp;
}

void TerrainManager::ExpandDetailsTRSBuffer() {
	TILERENDERSPEC *tmp = new TILERENDERSPEC [tbufsize_det+64];
	memcpy( tmp, DetailsTileList, sizeof(TILERENDERSPEC)*tbufsize_det);
	delete [ ] DetailsTileList;
	memset( tmp+tbufsize_det, 0, sizeof(TILERENDERSPEC)*64 );
	tbufsize_det += 64;
	DetailsTileList = tmp;
}

UINT8 GetMonth( double MJD ) {
	double Y = (((MJD - 44.0)/365.25) + 1859.0);
	Y -= double((UINT)Y);
	return (UINT)(Y/0.0833333);
}

TILEDESC1 *TerrainManager::NewTile( float amin, float amax ) {
	TILEDESC1 *td = new TILEDESC1;
	memset( td, 0, sizeof(TILEDESC1) );

	//extend tilebuffer if needed
	/*if( nbuf == bsize ) {
		TILEDESC1 **tmp = new TILEDESC1 *[bsize+32];
		if( nbuf ) {
			memcpy( tmp, buf, sizeof(TILEDESC1*)*bsize );
			delete [ ] buf;
		}
		memset( tmp+bsize, 0, sizeof(TILEDESC1*)*32 );
		buf = tmp;
		bsize += 32;
	}

	//write new tile into tilebuffer
	buf[nbuf] = td;*/
	td->ofs = buf.size();
	buf.push_back(td);
	td->amin = amin;
	td->amax = amax;
	td->flags |= RECOMPUTEBS;
	//nbuf++;
	return td;
}

void TerrainManager::DeleteSubTiles( TILEDESC1 *tile ) {
	for( UINT j = 0; j < 4; j++ )
		if( tile->subtile[j] ) {
			if( DeleteTile( tile->subtile[j] ) )
				tile->subtile[j] = NULL;
		}
}

bool TerrainManager::DeleteTile( TILEDESC1 *tile ) {
	bool del = true;
	for( UINT j = 0; j < 4; j++ ) {
		if( tile->subtile[j] ) {
			if( DeleteTile( tile->subtile[j] ) )
				tile->subtile[j] = NULL;
			else
				del = false;
		}
	}
	if( (tile->tex && (!tile->tex->IsUnloadable() || (tile->tex->GetOffset() != 0)))  || tile->ntex || tile->htex || tile->HMAP || tile->ctex || !del )
		return false;
	else {		
		auto res = std::find(buf.begin(), buf.end(), tile);
		buf.erase(res);
		//buf.erase(buf.at(tile->ofs));
		/*buf[nbuf-1]->ofs = tile->ofs;
		buf[tile->ofs] = buf[nbuf-1];
		buf[nbuf-1] = NULL;
		nbuf--;*/
		delete tile;
		tile = NULL;
		return true;
	}
}

void TerrainManager::ChangeTextureSet( UINT8 month ) {
	for( UINT j = 0; j < buf.size(); j++ ) {
		buf[j]->tex_old.swap(buf[j]->tex);
		buf[j]->tex = NULL;
		buf[j]->flags = REFRESH_TEX;
	}
}

void TerrainManager::CreateShadowsTileList()
{
	const D3DXMATRIX *shadowView[4] = {
		SC->GetShadowViewMatrix( 0 ),
		SC->GetShadowViewMatrix( 1 ),
		SC->GetShadowViewMatrix( 2 ),
		SC->GetShadowViewMatrix( 3 )
	};

	const float dim[4] = {
		20000.0f,
		2000.0f,
		250.0f,
		40.0f
	};

	const float zmax[4] = {
		40000.0f,
		4000.0f,
		500.0f,
		100.0f
	};

	float bsRad;
	bool  visible = false;
	D3DXVECTOR3 bsPosW, bsPosV;
	int ncascade = 0;
	for( int j = 0; j < tiles_noculling.size(); j++ )
	{
		if( !tiles_noculling[j].Tex.htex )
			continue;

		D3DXVECTOR3 &bsPosL = tiles_noculling[j].td->bsPos;

		//transform BS position into world space
		D3DXVec3TransformCoord( &bsPosW, &bsPosL, &tiles_noculling[j].mWorld );

		//get BS radius in meters
		bsRad = tiles_noculling[j].td->bsRad*(OBJ->rad/1000.0f);//*RP.dist_scale;

		ncascade = 0;
		for( int cascade = 0; cascade < 4; cascade++ )
		{
			//transform BS position into shadow View of current cascade
			D3DXVec3TransformCoord( &bsPosV, &bsPosL, shadowView[cascade] );

			if(
				bsPosV.x + bsRad > -1.0f*dim[cascade] ||
				bsPosV.x - bsRad < dim[cascade] ||
				bsPosV.y + bsRad > -1.0f*dim[cascade] ||
				bsPosV.y - bsRad < dim[cascade] ||
				bsPosV.z + bsRad > 1.0f ||
				bsPosV.z - bsRad < zmax[cascade] )	ncascade++;
			else									break;
		}

		if( ncascade )
		{
			tiles_noculling[j].ncascade = ncascade;
			tiles_shadows.push_back( tiles_noculling[j] );
		}
	}
}

void TerrainManager::CreateCameraTileList()
{
	for( int j = 0; j < tiles_noculling.size(); j++ )
	{
		if( IsTileVisible( tiles_noculling[j].td, &tiles_noculling[j].mWorld, RP.dist_scale ) )
			tiles_camera.push_back( tiles_noculling[j] );
	}
}
